#include "SerialPort.h"
#include <QPushButton>
#include <QLabel>
#include <QComboBox>
#include <QGroupBox>
#include <QTextEdit>
#include <QCheckBox>
#include <QSpinBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QSettings>
#include <QDebug>
#include <QDateTime>
#include <QTimer>
#include <QAbstractItemView>
#include <QScrollBar>
#include <QFile>
#include <QApplication>

#define SETTINGS_FILE_PATH "settings.ini"

static QMap<QSerialPort::BaudRate, QString> m_mapBbuadRate = {
    {QSerialPort::Baud1200  ,   "1200"  },
    {QSerialPort::Baud2400  ,   "2400"  },
    {QSerialPort::Baud4800  ,   "4800"  },
    {QSerialPort::Baud9600  ,   "9600"  },
    {QSerialPort::Baud19200 ,   "19200" },
    {QSerialPort::Baud38400 ,   "38400" },
    {QSerialPort::Baud57600 ,   "57600" },
    {QSerialPort::Baud115200,   "115200"},
};

static QMap<QSerialPort::DataBits, QString> m_mapDataBits = {
    {QSerialPort::Data5,    "5"},
    {QSerialPort::Data6,    "6"},
    {QSerialPort::Data7,    "7"},
    {QSerialPort::Data8,    "8"},
};

static QMap<QSerialPort::Parity, QString> m_mapParity = {
    {QSerialPort::NoParity   ,  "None" },
    {QSerialPort::EvenParity ,  "Even" },
    {QSerialPort::OddParity  ,  "Odd"  },
    {QSerialPort::SpaceParity,  "Space"},
    {QSerialPort::MarkParity ,  "Mark" },
};

static QMap<QSerialPort::StopBits, QString> m_mapStopBits = {
    {QSerialPort::OneStop       ,   "1"  },
    {QSerialPort::OneAndHalfStop,   "1.5"},
    {QSerialPort::TwoStop       ,   "2"  },
};

static QMap<QSerialPort::FlowControl, QString> m_mapFlowControl = {
    {QSerialPort::NoFlowControl  ,  "None"    },
    {QSerialPort::HardwareControl,  "Hardware"},
    {QSerialPort::SoftwareControl,  "Software"},
};

SerialPort::SerialPort(QWidget *parent) : QWidget(parent)
{
    initWidget();

    m_serialPort = new QSerialPort(this);
    connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPort::slot_comRecieve);
    connect(m_serialPort, &QSerialPort::errorOccurred, this, [=](QSerialPort::SerialPortError error){
        if(QSerialPort::ResourceError == error || QSerialPort::PermissionError == error){
            slot_updateComs();
        }
    });
    QString comName, baudRate, dataBits, parity, stopBits, flowCtrl;
    QString settingsFilePath(QString("%1/%2").arg(QApplication::applicationDirPath()).arg(SETTINGS_FILE_PATH));
    if(QFile::exists(settingsFilePath)){
        QSettings settings(settingsFilePath, QSettings::IniFormat);
        settings.beginGroup("COMSettings");
        comName  = settings.value("COMName").toString();
        baudRate = settings.value("BaudRate").toString();
        dataBits = settings.value("DataBits").toString();
        parity   = settings.value("Parity").toString();
        stopBits = settings.value("StopBits").toString();
        flowCtrl = settings.value("FlowControl").toString();
        settings.endGroup();
    }
    else{
        baudRate = m_mapBbuadRate.value(QSerialPort::Baud115200);
        dataBits = m_mapDataBits.value(QSerialPort::Data8);
        parity   = m_mapParity.value(QSerialPort::NoParity);
        stopBits = m_mapStopBits.value(QSerialPort::OneStop);
        flowCtrl = m_mapFlowControl.value(QSerialPort::NoFlowControl);
    }

    m_comSetControls.value(ComName)->setCurrentText(m_comsMap.value(comName));
    m_comSetControls.value(BaudRate)->setCurrentText(baudRate);
    m_comSetControls.value(DataBits)->setCurrentText(dataBits);
    m_comSetControls.value(Parity)->setCurrentText(parity);
    m_comSetControls.value(StopBits)->setCurrentText(stopBits);
    m_comSetControls.value(FlowControl)->setCurrentText(flowCtrl);

    m_comSettings.sendHex = false;
    m_comSettings.showHex = false;
    m_comSettings.showTime = false;

    m_autoSendTimer = new QTimer(this);
    connect(m_autoSendTimer, &QTimer::timeout, this, &SerialPort::slot_comSend);

    slot_wordWrap(false);
    updateControlState(false);
}

SerialPort::~SerialPort()
{
    if(m_serialPort && m_serialPort->isOpen()){
        m_serialPort->close();
        m_serialPort->deleteLater();
    }
}

void SerialPort::initWidget()
{
    QStringList lbStrings;
    lbStrings<<"Com Name"<<"Baud Rate"<<"Data Bits"<<"Parity"<<"Stop Bits"<<"Flow Control";

    QVector<QStringList> comboValues;
    comboValues<<getAvailableComs()<<m_mapBbuadRate.values()<<m_mapDataBits.values()<<m_mapParity.values()<<m_mapStopBits.values()<<m_mapFlowControl.values();

    QGroupBox *group_comSet = new QGroupBox("COM Settings", this);
    QGridLayout *layout_comSet = new QGridLayout(group_comSet);
    for(int i=0; i<lbStrings.size(); i++){
        QLabel *lb = new QLabel(lbStrings.at(i), this);
        QComboBox *combox = new QComboBox(this);
        combox->addItems(comboValues.at(i));
        layout_comSet->addWidget(lb, i, 0);
        if(0 == i){
            m_comboxComs = combox;
            m_comboxComs->setFixedWidth(100);
            updateComNamesComboxView(comboValues.at(i));
            layout_comSet->addWidget(combox, i, 1, 1, 1);
            QPushButton *btnUpdate = new QPushButton(this);
            btnUpdate->setIcon(QIcon(":/image/update.png"));
            layout_comSet->addWidget(btnUpdate, i, 2, 1, 1);
            connect(btnUpdate, &QPushButton::clicked, this, &SerialPort::slot_updateComs);
        }
        else{
            layout_comSet->addWidget(combox, i, 1, 1, 2);
        }

        m_comSetControls.insert(ComSetIndex(ComName + i), combox);
    }

    m_btnOpen = new QPushButton("Open", this);
    layout_comSet->addWidget(m_btnOpen, lbStrings.size(), 0, 1, 3, Qt::AlignCenter);
    layout_comSet->setRowStretch(lbStrings.size() + 1, 1);
    layout_comSet->setColumnStretch(1, 1);

    QGroupBox *group_recvSet = new QGroupBox("Recieve Settings", this);
    QGridLayout *layout_recvSet = new QGridLayout(group_recvSet);
    QCheckBox *check_hexRecv = new QCheckBox("Hex Recieve", this);
    QCheckBox *check_showTime = new QCheckBox("Show Time", this);
    QCheckBox *check_wordWrap = new QCheckBox("Word Wrap", this);
    layout_recvSet->addWidget(check_showTime, 0, 0);
    layout_recvSet->addWidget(check_hexRecv, 1, 0);
    layout_recvSet->addWidget(check_wordWrap, 2, 0);

    QGroupBox *group_sendSet = new QGroupBox("Send Settings", this);
    QGridLayout *layout_sendSet = new QGridLayout(group_sendSet);
    QCheckBox *check_hexSend = new QCheckBox("Hex Send", this);
    QCheckBox *check_autoSend = new QCheckBox("Auto Send", this);
    m_spinBoxSendMs = new QSpinBox(this);
    m_spinBoxSendMs->setSuffix(" ms");
    m_spinBoxSendMs->setRange(1, 9999);
    m_spinBoxSendMs->setValue(1000);
    layout_sendSet->addWidget(check_hexSend, 0, 0, 1, 2);
    layout_sendSet->addWidget(check_autoSend, 1, 0, 1, 1);
    layout_sendSet->addWidget(m_spinBoxSendMs, 1, 1, 1, 1);

    QPushButton *btn_clearRecv = new QPushButton("Clear Recieve", this);
    QPushButton *btn_clearSend = new QPushButton("Clear Send", this);
    QHBoxLayout *layout_clearBtns = new QHBoxLayout;
    layout_clearBtns->addWidget(btn_clearRecv);
    layout_clearBtns->addWidget(btn_clearSend);

    QVBoxLayout *layout_settings = new QVBoxLayout;
    layout_settings->addWidget(group_comSet);
    layout_settings->addWidget(group_recvSet);
    layout_settings->addWidget(group_sendSet);
    layout_settings->addLayout(layout_clearBtns);
    layout_settings->addStretch(1);

    QGroupBox *group_recv = new QGroupBox("Recieve Data", this);
    QGridLayout *layout_recv = new QGridLayout(group_recv);
    m_textRecv = new QTextEdit(this);
    layout_recv->addWidget(m_textRecv);
    m_textRecv->setReadOnly(true);

    QGroupBox *group_send = new QGroupBox("Send Data", this);
    QGridLayout *layou_send = new QGridLayout(group_send);
    m_textSend = new QTextEdit(this);
    m_btnSend = new QPushButton("Send", this);
    layou_send->addWidget(m_textSend, 0, 0);
    layou_send->addWidget(m_btnSend, 0, 1);
    layou_send->setColumnStretch(0, 1);
    m_textSend->setMaximumHeight(80);

    QGridLayout *layout_surface = new QGridLayout(this);
    layout_surface->addLayout(layout_settings, 0, 0, 2, 1);
    layout_surface->addWidget(group_recv, 0, 1, 1, 1);
    layout_surface->addWidget(group_send, 1, 1, 1, 1);
    layout_surface->setRowStretch(0, 1);
    layout_surface->setColumnStretch(1, 1);

    connect(m_btnOpen, &QPushButton::clicked, this, &SerialPort::slot_comOpen);
    connect(m_btnSend, &QPushButton::clicked, this, &SerialPort::slot_comSend);
    connect(check_hexRecv, &QCheckBox::stateChanged, this, &SerialPort::slot_hexRecieve);
    connect(check_showTime, &QCheckBox::stateChanged, this, &SerialPort::slot_showTime);
    connect(check_wordWrap, &QCheckBox::stateChanged, this, &SerialPort::slot_wordWrap);
    connect(check_hexSend, &QCheckBox::stateChanged, this, &SerialPort::slot_hexSend);
    connect(check_autoSend, &QCheckBox::stateChanged, this, &SerialPort::slot_autoSend);
    connect(btn_clearRecv, &QPushButton::clicked, m_textRecv, &QTextEdit::clear);
    connect(btn_clearSend, &QPushButton::clicked, m_textSend, &QTextEdit::clear);
}

QStringList SerialPort::getAvailableComs()
{
    m_comsMap.clear();
    QList<QSerialPortInfo> infoList = QSerialPortInfo::availablePorts();
    for(QSerialPortInfo info : infoList){
//        if(!info.isValid()){
//            continue;
//        }
        m_comsMap.insert(info.portName(), QString("%1(%2)").arg(info.description()).arg(info.portName()));
    }
    return m_comsMap.values();
}

void SerialPort::updateControlState(bool connected)
{
    for(QComboBox *ctl : m_comSetControls.values()){
        ctl->setEnabled(!connected);
    }
    m_btnOpen->setText(connected ? "Close" : "Open");
    m_btnSend->setEnabled(connected);
}

void SerialPort::addLog(QString log)
{
    if(m_comSettings.showTime){
        log = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss:zzz: ") + log;
    }
    m_textRecv->setText(m_textRecv->toPlainText() + log);
    m_textRecv->moveCursor(QTextCursor::End);
}

void SerialPort::slot_comOpen()
{
    if(!m_serialPort->isOpen()){
        QString comName = m_comSetControls.value(ComName)->currentText();
        comName = m_comsMap.key(comName);
        if(!comName.isEmpty()){
            QString baudRate = m_comSetControls.value(BaudRate)->currentText();
            QString dataBits = m_comSetControls.value(DataBits)->currentText();
            QString parity = m_comSetControls.value(Parity)->currentText();
            QString stopBits = m_comSetControls.value(StopBits)->currentText();
            QString flowCtrl = m_comSetControls.value(FlowControl)->currentText();

            m_serialPort->setPortName(comName);
            m_serialPort->setBaudRate(m_mapBbuadRate.key(baudRate));
            m_serialPort->setDataBits(m_mapDataBits.key(dataBits));
            m_serialPort->setParity(m_mapParity.key(parity));
            m_serialPort->setStopBits(m_mapStopBits.key(stopBits));
            m_serialPort->setFlowControl(m_mapFlowControl.key(flowCtrl));
            if(m_serialPort->open(QSerialPort::ReadWrite)){
                addLog("Com Open Succeed.\r\n");
                QSettings settings("settings.ini", QSettings::IniFormat);
                settings.beginGroup("COMSettings");
                settings.setValue("COMName", comName);
                settings.setValue("BaudRate", baudRate);
                settings.setValue("DataBits", dataBits);
                settings.setValue("Parity", parity);
                settings.setValue("StopBits", stopBits);
                settings.setValue("FlowControl", flowCtrl);
                settings.endGroup();
                emit signalUpdateTitle("SerialPort-" + comName);
            }
            else{
                addLog("Com Open Failed: " + m_serialPort->errorString() + "\r\n");
            }
        }
        else{
            addLog("Com Invalid.\r\n");
        }
    }
    else{
        addLog("Com Close Succeed.\r\n");
        m_serialPort->close();
    }
    updateControlState(m_serialPort->isOpen());
}

void SerialPort::slot_comRecieve()
{
    QByteArray data(m_serialPort->readAll());
    if(data.simplified().isEmpty()){
        return;
    }
    if(m_comSettings.showHex){
        addLog(data.toHex(' '));
    }
    else{
        addLog(QString::fromLocal8Bit(data));
    }
}

void SerialPort::slot_comSend()
{
    if(!m_serialPort->isOpen()){
        return;
    }

    QString text = m_textSend->toPlainText();
    if(text.isEmpty()){
        return;
    }

    QByteArray data = text.toLocal8Bit();
    if(m_comSettings.sendHex){
        data = QByteArray::fromHex(data);
    }
    m_serialPort->write(data);
    addLog(text + "\r\n");
}

void SerialPort::slot_hexRecieve(int state)
{
    m_comSettings.showHex = (Qt::Checked == state);
}

void SerialPort::slot_showTime(int state)
{
    m_comSettings.showTime = (Qt::Checked == state);
}

void SerialPort::slot_wordWrap(int state)
{
    m_textRecv->setWordWrapMode(Qt::Checked == state ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
}

void SerialPort::slot_hexSend(int state)
{
    m_comSettings.sendHex = (Qt::Checked == state);
}

void SerialPort::slot_autoSend(int state)
{
    bool autoSend(Qt::Checked == state);
    if(autoSend){
        m_autoSendTimer->setInterval(m_spinBoxSendMs->value());
        m_autoSendTimer->start();
    }
    else{
        m_autoSendTimer->stop();
    }
    m_spinBoxSendMs->setEnabled(!autoSend);
}

void SerialPort::slot_updateComs()
{
    m_comboxComs->clear();
    QString curCom = m_comboxComs->currentText();
    QStringList tmpComs = getAvailableComs();
    m_comboxComs->addItems(tmpComs);
    if(tmpComs.contains(curCom)){
        m_comboxComs->setCurrentText(curCom);
    }
    else{
        if(m_serialPort->isOpen()){
            m_serialPort->close();
            updateControlState(false);
        }
    }

    updateComNamesComboxView(tmpComs);
}

void SerialPort::updateComNamesComboxView(QStringList tmpComs)
{
    int maxWith = 0,curWith = 0;
    QFontMetrics fm(m_comboxComs->font());
    foreach(QString tmpCom, tmpComs){
        curWith = fm.width(tmpCom);
        maxWith = qMax(maxWith, curWith);
    }
    maxWith += m_comboxComs->view()->verticalScrollBar()->depth();
    m_comboxComs->view()->setMinimumWidth(maxWith);
}
